package io.craft.atom.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * A http utility class that sends http request and parse response.
 * 
 * @author mindwind
 * @version 1.0, 2011-10-18
 */
public class HttpUtil {

	
	public  static final String              DEFAULT_CHARSET      = "UTF-8"                                     ;
	public  static final String              DEFAULT_CONTENT_TYPE = "application/x-www-form-urlencoded;charset=";
	private static final Map<String, String> DEFAULT_HEADER       = new HashMap<String, String>()               ;

	
	static {
		DEFAULT_HEADER.put("User-Agent", "http-util");
		DEFAULT_HEADER.put("Accept", "text/xml,text/javascript,text/html");
	}
	

	/**
	 * Build http query string.
	 * 
	 * @param params
	 * @param charset
	 * @return http query string
	 * @throws UnsupportedEncodingException
	 */
	public static String buildQuery(Map<String, String> params, String charset) throws UnsupportedEncodingException {
		if (params == null || params.isEmpty()) { return null; }

		StringBuilder query = new StringBuilder();
		Set<Entry<String, String>> entries = params.entrySet();
		boolean hasParam = false;
		for (Entry<String, String> entry : entries) {
			String name = entry.getKey();
			String value = entry.getValue();
			if (StringUtil.isAllNotEmpty(name, value)) {
				if (hasParam) {
					query.append("&");
				} else {
					hasParam = true;
				}

				query.append(name).append("=").append(URLEncoder.encode(value, charset));
			}
		}

		return query.toString();
	}

	/**
	 * Execute http post request, using query string as post content with
	 * default charset(UTF-8).
	 * 
	 * @param url
	 * @param params
	 * @param connectTimeout
	 *            an int that specifies the connect timeout value in milliseconds
	 * @param readTimeout
	 * 			  an int that specifies the read timeout value in milliseconds
	 * @return post response
	 * @throws Exception
	 */
	public static String doPost(String url, Map<String, String> params, int connectTimeout, int readTimeout) throws Exception {
		return doPost(url, params, DEFAULT_CHARSET, connectTimeout, readTimeout);
	}

	/**
	 * Execute http post request, using query string as post content with
	 * dedicated charset.
	 * 
	 * @param url
	 * @param params
	 * @param charset
	 * @param connectTimeout
	 * 			  an int that specifies the connect timeout value in milliseconds
	 * @param readTimeout
	 *            an int that specifies the read timeout value in milliseconds
	 * @return post response
	 * @throws Exception
	 */
	public static String doPost(String url, Map<String, String> params, String charset, int connectTimeout, int readTimeout) throws Exception {
		String ctype = DEFAULT_CONTENT_TYPE + charset;
		String query = buildQuery(params, charset);
		byte[] content = {};
		if (query != null) {
			content = query.getBytes(charset);
		}
		
		return doPost(url, ctype, content, DEFAULT_HEADER, connectTimeout, readTimeout);
	}

	/**
	 * Execute http post request, use dedicated content and content type with
	 * dedicated header.
	 * 
	 * @param url
	 * @param contentType
	 * @param content
	 * @param connectTimeout
	 *            an int that specifies the connect timeout value in milliseconds
	 * @param readTimeout
	 *            an int that specifies the read timeout value in milliseconds
	 * @param header
	 * @return post response
	 * @throws IOException
	 */
	public static String doPost(String url, String contentType, byte[] content, Map<String, String> header, int connectTimeout, int readTimeout) throws IOException {
		HttpURLConnection conn = null;
		OutputStream out = null;
		String rsp = null;
		try {
			conn = getConnection(new URL(url), "POST", contentType, header);
			conn.setConnectTimeout(connectTimeout);
			conn.setReadTimeout(readTimeout);

			out = conn.getOutputStream();
			out.write(content);
			rsp = getResponseAsString(conn);
		} finally {
			if (out != null) {
				out.close();
			}
			if (conn != null) {
				conn.disconnect();
			}
		}

		return rsp;
	}

	private static HttpURLConnection getConnection(URL url, String method, String ctype, Map<String, String> header) throws IOException {
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		conn.setRequestMethod(method);
		conn.setDoInput(true);
		conn.setDoOutput(true);
		conn.setRequestProperty("Content-Type", ctype);

		if (header != null) {
			for (Map.Entry<String, String> entry : header.entrySet()) {
				conn.setRequestProperty(entry.getKey(), entry.getValue());
			}
		}

		return conn;
	}

	private static String getResponseAsString(HttpURLConnection conn) throws IOException {
		String charset = getResponseCharset(conn.getContentType());
		InputStream es = conn.getErrorStream();
		if (es == null) {
			return getStreamAsString(conn.getInputStream(), charset);
		} else {
			String msg = getStreamAsString(es, charset);
			if (StringUtil.isEmpty(msg)) {
				throw new IOException(conn.getResponseCode() + ":" + conn.getResponseMessage());
			} else {
				throw new IOException(msg);
			}
		}
	}

	private static String getResponseCharset(String ctype) {
		String charset = DEFAULT_CHARSET;

		if (!StringUtil.isEmpty(ctype)) {
			String[] params = ctype.split(";");
			for (String param : params) {
				param = param.trim();
				if (param.startsWith("charset")) {
					String[] pair = param.split("=", 2);
					if (pair.length == 2 && !StringUtil.isEmpty(pair[1])) {
						charset = pair[1].trim();
					}
					break;
				}
			}
		}
		return charset;
	}

	private static String getStreamAsString(InputStream stream, String charset) throws IOException {
		try {
			BufferedReader reader = new BufferedReader(new InputStreamReader(stream, charset));
			StringWriter writer = new StringWriter();

			char[] chars = new char[256];
			int count = 0;
			while ((count = reader.read(chars)) > 0) {
				writer.write(chars, 0, count);
			}

			return writer.toString();
		} finally {
			if (stream != null) {
				stream.close();
			}
		}
	}

	private HttpUtil() {
		throw new UnsupportedOperationException();
	}

}
